/* convert.c - convert RImage to Pixmap
 *
 * Raster graphics library
 *
 * Copyright (c) 1997-2003 Alfredo K. Kojima
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
 *  MA 02110-1301, USA.
 */

/* Problems:
 *   1. Using Grayscale visual with Dithering crashes wmaker
 *   2. Ghost dock/appicon is wrong in Pseudocolor, Staticgray, Grayscale
 */

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "config.h"
#include "wraster.h"
#include "convert.h"
#include "xutil.h"

#define NFREE(n) \
  if (n)         \
  free(n)

#define HAS_ALPHA(I) ((I)->format == RRGBAFormat)

typedef struct RConversionTable {
  unsigned short table[256];
  unsigned short index;

  struct RConversionTable *next;
} RConversionTable;

typedef struct RStdConversionTable {
  unsigned int table[256];

  unsigned short mult;
  unsigned short max;

  struct RStdConversionTable *next;
} RStdConversionTable;

static RConversionTable *conversionTable = NULL;
static RStdConversionTable *stdConversionTable = NULL;

static void release_conversion_table(void)
{
  RConversionTable *tmp = conversionTable;

  while (tmp) {
    RConversionTable *tmp_to_delete = tmp;

    tmp = tmp->next;
    free(tmp_to_delete);
  }
  conversionTable = NULL;
}

static void release_std_conversion_table(void)
{
  RStdConversionTable *tmp = stdConversionTable;

  while (tmp) {
    RStdConversionTable *tmp_to_delete = tmp;

    tmp = tmp->next;
    free(tmp_to_delete);
  }
  stdConversionTable = NULL;
}

void r_destroy_conversion_tables(void)
{
  release_conversion_table();
  release_std_conversion_table();
}

static unsigned short *computeTable(unsigned short mask)
{
  RConversionTable *tmp = conversionTable;
  int i;

  while (tmp) {
    if (tmp->index == mask)
      break;
    tmp = tmp->next;
  }

  if (tmp)
    return tmp->table;

  tmp = (RConversionTable *)malloc(sizeof(RConversionTable));
  if (tmp == NULL)
    return NULL;

  for (i = 0; i < 256; i++)
    tmp->table[i] = (i * mask + 0x7f) / 0xff;

  tmp->index = mask;
  tmp->next = conversionTable;
  conversionTable = tmp;
  return tmp->table;
}

static unsigned int *computeStdTable(unsigned int mult, unsigned int max)
{
  RStdConversionTable *tmp = stdConversionTable;
  unsigned int i;

  while (tmp) {
    if (tmp->mult == mult && tmp->max == max)
      break;
    tmp = tmp->next;
  }

  if (tmp)
    return tmp->table;

  tmp = (RStdConversionTable *)malloc(sizeof(RStdConversionTable));
  if (tmp == NULL)
    return NULL;

  for (i = 0; i < 256; i++) {
    tmp->table[i] = (i * max) / 0xff * mult;
  }
  tmp->mult = mult;
  tmp->max = max;

  tmp->next = stdConversionTable;
  stdConversionTable = tmp;

  return tmp->table;
}

/***************************************************************************/

static void convertTrueColor_generic(RXImage *ximg, RImage *image, signed char *err,
                                     signed char *nerr, const unsigned short *rtable,
                                     const unsigned short *gtable, const unsigned short *btable,
                                     const int dr, const int dg, const int db,
                                     const unsigned short roffs, const unsigned short goffs,
                                     const unsigned short boffs)
{
  signed char *terr;
  int x, y, r, g, b;
  int pixel;
  int rer, ger, ber;
  unsigned char *ptr = image->data;
  int channels = (HAS_ALPHA(image) ? 4 : 3);

  /* convert and dither the image to XImage */
  for (y = 0; y < image->height; y++) {
    nerr[0] = 0;
    nerr[1] = 0;
    nerr[2] = 0;
    for (x = 0; x < image->width; x++, ptr += channels) {
      /* reduce pixel */
      pixel = *ptr + err[x];
      if (pixel < 0)
        pixel = 0;
      else if (pixel > 0xff)
        pixel = 0xff;
      r = rtable[pixel];
      /* calc error */
      rer = pixel - r * dr;

      /* reduce pixel */
      pixel = *(ptr + 1) + err[x + 1];
      if (pixel < 0)
        pixel = 0;
      else if (pixel > 0xff)
        pixel = 0xff;
      g = gtable[pixel];
      /* calc error */
      ger = pixel - g * dg;

      /* reduce pixel */
      pixel = *(ptr + 2) + err[x + 2];
      if (pixel < 0)
        pixel = 0;
      else if (pixel > 0xff)
        pixel = 0xff;
      b = btable[pixel];
      /* calc error */
      ber = pixel - b * db;

      pixel = (r << roffs) | (g << goffs) | (b << boffs);
      XPutPixel(ximg->image, x, y, pixel);

      /* distribute error */
      r = (rer * 3) / 8;
      g = (ger * 3) / 8;
      b = (ber * 3) / 8;
      /* x+1, y */
      err[x + 3 * 1] += r;
      err[x + 1 + 3 * 1] += g;
      err[x + 2 + 3 * 1] += b;
      /* x, y+1 */
      nerr[x] += r;
      nerr[x + 1] += g;
      nerr[x + 2] += b;
      /* x+1, y+1 */
      nerr[x + 3 * 1] = rer - 2 * r;
      nerr[x + 1 + 3 * 1] = ger - 2 * g;
      nerr[x + 2 + 3 * 1] = ber - 2 * b;
    }
    /* skip to next line */
    terr = err;
    err = nerr;
    nerr = terr;
  }

  /* redither the 1st line to distribute error better */
  ptr = image->data;
  y = 0;
  nerr[0] = 0;
  nerr[1] = 0;
  nerr[2] = 0;
  for (x = 0; x < image->width; x++, ptr += channels) {
    /* reduce pixel */
    pixel = *ptr + err[x];
    if (pixel < 0)
      pixel = 0;
    else if (pixel > 0xff)
      pixel = 0xff;
    r = rtable[pixel];
    /* calc error */
    rer = pixel - r * dr;

    /* reduce pixel */
    pixel = *(ptr + 1) + err[x + 1];
    if (pixel < 0)
      pixel = 0;
    else if (pixel > 0xff)
      pixel = 0xff;
    g = gtable[pixel];
    /* calc error */
    ger = pixel - g * dg;

    /* reduce pixel */
    pixel = *(ptr + 2) + err[x + 2];
    if (pixel < 0)
      pixel = 0;
    else if (pixel > 0xff)
      pixel = 0xff;
    b = btable[pixel];
    /* calc error */
    ber = pixel - b * db;

    pixel = (r << roffs) | (g << goffs) | (b << boffs);
    XPutPixel(ximg->image, x, y, pixel);

    /* distribute error */
    r = (rer * 3) / 8;
    g = (ger * 3) / 8;
    b = (ber * 3) / 8;
    /* x+1, y */
    err[x + 3 * 1] += r;
    err[x + 1 + 3 * 1] += g;
    err[x + 2 + 3 * 1] += b;
    /* x, y+1 */
    nerr[x] += r;
    nerr[x + 1] += g;
    nerr[x + 2] += b;
    /* x+1, y+1 */
    nerr[x + 3 * 1] = rer - 2 * r;
    nerr[x + 1 + 3 * 1] = ger - 2 * g;
    nerr[x + 2 + 3 * 1] = ber - 2 * b;
  }
}

static RXImage *image2TrueColor(RContext *ctx, RImage *image)
{
  RXImage *ximg;
  unsigned short rmask, gmask, bmask;
  unsigned short roffs, goffs, boffs;
  unsigned short *rtable, *gtable, *btable;
  int channels = (HAS_ALPHA(image) ? 4 : 3);

  ximg = RCreateXImage(ctx, ctx->depth, image->width, image->height);
  if (!ximg) {
    return NULL;
  }

  roffs = ctx->red_offset;
  goffs = ctx->green_offset;
  boffs = ctx->blue_offset;

  rmask = ctx->visual->red_mask >> roffs;
  gmask = ctx->visual->green_mask >> goffs;
  bmask = ctx->visual->blue_mask >> boffs;

  rtable = computeTable(rmask);
  gtable = computeTable(gmask);
  btable = computeTable(bmask);

  if (rtable == NULL || gtable == NULL || btable == NULL) {
    RErrorCode = RERR_NOMEMORY;
    RDestroyXImage(ctx, ximg);
    return NULL;
  }

  if (ctx->attribs->render_mode == RBestMatchRendering) {
    int ofs;
    unsigned long r, g, b;
    int x, y;
    unsigned long pixel;
    unsigned char *ptr = image->data;

    /* fake match */
#ifdef WRLIB_DEBUG
    fputs("true color match\n", stderr);
#endif
    if (rmask == 0xff && gmask == 0xff && bmask == 0xff) {
      for (y = 0; y < image->height; y++) {
        for (x = 0; x < image->width; x++, ptr += channels) {
          /* reduce pixel */
          r = ptr[0];
          g = ptr[1];
          b = ptr[2];
          pixel = (r << roffs) | (g << goffs) | (b << boffs);
          XPutPixel(ximg->image, x, y, pixel);
        }
      }
    } else {
      for (y = 0, ofs = 0; y < image->height; y++) {
        for (x = 0; x < image->width; x++, ofs += channels - 3) {
          /* reduce pixel */
          r = rtable[ptr[ofs++]];
          g = gtable[ptr[ofs++]];
          b = btable[ptr[ofs++]];
          pixel = (r << roffs) | (g << goffs) | (b << boffs);
          XPutPixel(ximg->image, x, y, pixel);
        }
      }
    }
  } else {
    /* dither */
    const int dr = 0xff / rmask;
    const int dg = 0xff / gmask;
    const int db = 0xff / bmask;

#ifdef WRLIB_DEBUG
    fputs("true color dither\n", stderr);
#endif

    {
      signed char *err;
      signed char *nerr;
      int ch = (HAS_ALPHA(image) ? 4 : 3);

      err = malloc(ch * (image->width + 2));
      nerr = malloc(ch * (image->width + 2));
      if (!err || !nerr) {
        NFREE(err);
        NFREE(nerr);
        RErrorCode = RERR_NOMEMORY;
        RDestroyXImage(ctx, ximg);
        return NULL;
      }

      memset(err, 0, ch * (image->width + 2));
      memset(nerr, 0, ch * (image->width + 2));

      convertTrueColor_generic(ximg, image, err, nerr, rtable, gtable, btable, dr, dg, db, roffs,
                               goffs, boffs);
      free(err);
      free(nerr);
    }
  }

  return ximg;
}

/***************************************************************************/

static void convertPseudoColor_to_8(RXImage *ximg, RImage *image, signed char *err,
                                    signed char *nerr, const unsigned short *rtable,
                                    const unsigned short *gtable, const unsigned short *btable,
                                    const int dr, const int dg, const int db, unsigned long *pixels,
                                    int cpc)
{
  signed char *terr;
  int x, y, r, g, b;
  int pixel;
  int rer, ger, ber;
  unsigned char *ptr = image->data;
  unsigned char *optr = (unsigned char *)ximg->image->data;
  int channels = (HAS_ALPHA(image) ? 4 : 3);
  int cpcpc = cpc * cpc;

  /* convert and dither the image to XImage */
  for (y = 0; y < image->height; y++) {
    nerr[0] = 0;
    nerr[1] = 0;
    nerr[2] = 0;
    for (x = 0; x < image->width * 3; x += 3, ptr += channels) {
      /* reduce pixel */
      pixel = *ptr + err[x];
      if (pixel < 0)
        pixel = 0;
      else if (pixel > 0xff)
        pixel = 0xff;
      r = rtable[pixel];
      /* calc error */
      rer = pixel - r * dr;

      /* reduce pixel */
      pixel = *(ptr + 1) + err[x + 1];
      if (pixel < 0)
        pixel = 0;
      else if (pixel > 0xff)
        pixel = 0xff;
      g = gtable[pixel];
      /* calc error */
      ger = pixel - g * dg;

      /* reduce pixel */
      pixel = *(ptr + 2) + err[x + 2];
      if (pixel < 0)
        pixel = 0;
      else if (pixel > 0xff)
        pixel = 0xff;
      b = btable[pixel];
      /* calc error */
      ber = pixel - b * db;

      *optr++ = pixels[r * cpcpc + g * cpc + b];

      /* distribute error */
      r = (rer * 3) / 8;
      g = (ger * 3) / 8;
      b = (ber * 3) / 8;

      /* x+1, y */
      err[x + 3 * 1] += r;
      err[x + 1 + 3 * 1] += g;
      err[x + 2 + 3 * 1] += b;
      /* x, y+1 */
      nerr[x] += r;
      nerr[x + 1] += g;
      nerr[x + 2] += b;
      /* x+1, y+1 */
      nerr[x + 3 * 1] = rer - 2 * r;
      nerr[x + 1 + 3 * 1] = ger - 2 * g;
      nerr[x + 2 + 3 * 1] = ber - 2 * b;
    }
    /* skip to next line */
    terr = err;
    err = nerr;
    nerr = terr;

    optr += ximg->image->bytes_per_line - image->width;
  }
}

static RXImage *image2PseudoColor(RContext *ctx, RImage *image)
{
  RXImage *ximg;
  register int x, y, r, g, b;
  unsigned char *ptr;
  unsigned long pixel;
  const int cpc = ctx->attribs->colors_per_channel;
  const unsigned short rmask = cpc - 1; /* different sizes could be used */
  const unsigned short gmask = rmask;   /* for r,g,b */
  const unsigned short bmask = rmask;
  unsigned short *rtable, *gtable, *btable;
  const int cpccpc = cpc * cpc;
  int channels = (HAS_ALPHA(image) ? 4 : 3);

  ximg = RCreateXImage(ctx, ctx->depth, image->width, image->height);
  if (!ximg) {
    return NULL;
  }

  ptr = image->data;

  /* Tables are same at the moment because rmask==gmask==bmask. */
  rtable = computeTable(rmask);
  gtable = computeTable(gmask);
  btable = computeTable(bmask);

  if (rtable == NULL || gtable == NULL || btable == NULL) {
    RErrorCode = RERR_NOMEMORY;
    RDestroyXImage(ctx, ximg);
    return NULL;
  }

  if (ctx->attribs->render_mode == RBestMatchRendering) {
    /* fake match */
#ifdef WRLIB_DEBUG
    fprintf(stderr, "pseudo color match with %d colors per channel\n", cpc);
#endif
    for (y = 0; y < image->height; y++) {
      for (x = 0; x < image->width; x++, ptr += channels - 3) {
        /* reduce pixel */
        r = rtable[*ptr++];
        g = gtable[*ptr++];
        b = btable[*ptr++];
        pixel = r * cpccpc + g * cpc + b;
        /*data[ofs] = ctx->colors[pixel].pixel; */
        XPutPixel(ximg->image, x, y, ctx->colors[pixel].pixel);
      }
    }
  } else {
    /* dither */
    signed char *err;
    signed char *nerr;
    const int dr = 0xff / rmask;
    const int dg = 0xff / gmask;
    const int db = 0xff / bmask;

#ifdef WRLIB_DEBUG
    fprintf(stderr, "pseudo color dithering with %d colors per channel\n", cpc);
#endif
    err = malloc(4 * (image->width + 3));
    nerr = malloc(4 * (image->width + 3));
    if (!err || !nerr) {
      NFREE(err);
      NFREE(nerr);
      RErrorCode = RERR_NOMEMORY;
      RDestroyXImage(ctx, ximg);
      return NULL;
    }
    memset(err, 0, 4 * (image->width + 3));
    memset(nerr, 0, 4 * (image->width + 3));

    convertPseudoColor_to_8(ximg, image, err + 4, nerr + 4, rtable, gtable, btable, dr, dg, db,
                            ctx->pixels, cpc);

    free(err);
    free(nerr);
  }

  return ximg;
}

/*
 * For standard colormap
 */
static RXImage *image2StandardPseudoColor(RContext *ctx, RImage *image)
{
  RXImage *ximg;
  register int x, y, r, g, b;
  unsigned char *ptr;
  unsigned long pixel;
  unsigned char *data;
  unsigned int *rtable, *gtable, *btable;
  unsigned int base_pixel = ctx->std_rgb_map->base_pixel;
  int channels = (HAS_ALPHA(image) ? 4 : 3);

  ximg = RCreateXImage(ctx, ctx->depth, image->width, image->height);
  if (!ximg) {
    return NULL;
  }

  ptr = image->data;

  data = (unsigned char *)ximg->image->data;

  rtable = computeStdTable(ctx->std_rgb_map->red_mult, ctx->std_rgb_map->red_max);

  gtable = computeStdTable(ctx->std_rgb_map->green_mult, ctx->std_rgb_map->green_max);

  btable = computeStdTable(ctx->std_rgb_map->blue_mult, ctx->std_rgb_map->blue_max);

  if (rtable == NULL || gtable == NULL || btable == NULL) {
    RErrorCode = RERR_NOMEMORY;
    RDestroyXImage(ctx, ximg);
    return NULL;
  }

  if (ctx->attribs->render_mode == RBestMatchRendering) {
    for (y = 0; y < image->height; y++) {
      for (x = 0; x < image->width; x++, ptr += channels) {
        /* reduce pixel */
        pixel = (rtable[*ptr] + gtable[*(ptr + 1)] + btable[*(ptr + 2)] + base_pixel) & 0xffffffff;

        XPutPixel(ximg->image, x, y, pixel);
      }
    }
  } else {
    /* dither */
    signed short *err, *nerr;
    signed short *terr;
    int rer, ger, ber;
    int x1, ofs;

#ifdef WRLIB_DEBUG
    fprintf(stderr, "pseudo color dithering with %d colors per channel\n",
            ctx->attribs->colors_per_channel);
#endif
    err = (short *)malloc(3 * (image->width + 2) * sizeof(short));
    nerr = (short *)malloc(3 * (image->width + 2) * sizeof(short));
    if (!err || !nerr) {
      NFREE(err);
      NFREE(nerr);
      RErrorCode = RERR_NOMEMORY;
      RDestroyXImage(ctx, ximg);
      return NULL;
    }
    for (x = 0, x1 = 0; x < image->width * 3; x1 += channels - 3) {
      err[x++] = ptr[x1++];
      err[x++] = ptr[x1++];
      err[x++] = ptr[x1++];
    }
    err[x] = err[x + 1] = err[x + 2] = 0;
    /* convert and dither the image to XImage */
    for (y = 0, ofs = 0; y < image->height; y++) {
      if (y < image->height - 1) {
        int x1;
        for (x = 0, x1 = (y + 1) * image->width * channels; x < image->width * 3;
             x1 += channels - 3) {
          nerr[x++] = ptr[x1++];
          nerr[x++] = ptr[x1++];
          nerr[x++] = ptr[x1++];
        }
        /* last column */
        x1 -= channels;
        nerr[x++] = ptr[x1++];
        nerr[x++] = ptr[x1++];
        nerr[x++] = ptr[x1++];
      }
      for (x = 0; x < image->width * 3; x += 3, ofs++) {
        /* reduce pixel */
        if (err[x] > 0xff)
          err[x] = 0xff;
        else if (err[x] < 0)
          err[x] = 0;
        if (err[x + 1] > 0xff)
          err[x + 1] = 0xff;
        else if (err[x + 1] < 0)
          err[x + 1] = 0;
        if (err[x + 2] > 0xff)
          err[x + 2] = 0xff;
        else if (err[x + 2] < 0)
          err[x + 2] = 0;

        r = rtable[err[x]];
        g = gtable[err[x + 1]];
        b = btable[err[x + 2]];

        pixel = r + g + b;

        data[ofs] = base_pixel + pixel;

        /* calc error */
        rer = err[x] - (ctx->colors[pixel].red >> 8);
        ger = err[x + 1] - (ctx->colors[pixel].green >> 8);
        ber = err[x + 2] - (ctx->colors[pixel].blue >> 8);

        /* distribute error */
        err[x + 3 * 1] += (rer * 7) / 16;
        err[x + 1 + 3 * 1] += (ger * 7) / 16;
        err[x + 2 + 3 * 1] += (ber * 7) / 16;

        nerr[x] += (rer * 5) / 16;
        nerr[x + 1] += (ger * 5) / 16;
        nerr[x + 2] += (ber * 5) / 16;

        if (x > 0) {
          nerr[x - 3 * 1] += (rer * 3) / 16;
          nerr[x - 3 * 1 + 1] += (ger * 3) / 16;
          nerr[x - 3 * 1 + 2] += (ber * 3) / 16;
        }

        nerr[x + 3 * 1] += rer / 16;
        nerr[x + 1 + 3 * 1] += ger / 16;
        nerr[x + 2 + 3 * 1] += ber / 16;
      }
      /* skip to next line */
      terr = err;
      err = nerr;
      nerr = terr;

      ofs += ximg->image->bytes_per_line - image->width;
    }
    free(err);
    free(nerr);
  }
  ximg->image->data = (char *)data;

  return ximg;
}

static RXImage *image2GrayScale(RContext *ctx, RImage *image)
{
  RXImage *ximg;
  register int x, y, g;
  unsigned char *ptr;
  const int cpc = ctx->attribs->colors_per_channel;
  unsigned short gmask;
  unsigned short *table;
  unsigned char *data;
  int channels = (HAS_ALPHA(image) ? 4 : 3);

  ximg = RCreateXImage(ctx, ctx->depth, image->width, image->height);
  if (!ximg) {
    return NULL;
  }

  ptr = image->data;

  data = (unsigned char *)ximg->image->data;

  if (ctx->vclass == StaticGray)
    gmask = (1 << ctx->depth) - 1; /* use all grays */
  else
    gmask = cpc * cpc * cpc - 1;

  table = computeTable(gmask);

  if (table == NULL) {
    RErrorCode = RERR_NOMEMORY;
    RDestroyXImage(ctx, ximg);
    return NULL;
  }

  if (ctx->attribs->render_mode == RBestMatchRendering) {
    /* fake match */
#ifdef WRLIB_DEBUG
    fprintf(stderr, "grayscale match with %d colors per channel\n", cpc);
#endif
    for (y = 0; y < image->height; y++) {
      for (x = 0; x < image->width; x++) {
        /* reduce pixel */
        g = table[(*ptr * 30 + *(ptr + 1) * 59 + *(ptr + 2) * 11) / 100];
        ptr += channels;
        /*data[ofs] = ctx->colors[g].pixel; */
        XPutPixel(ximg->image, x, y, ctx->colors[g].pixel);
      }
    }
  } else {
    /* dither */
    short *gerr;
    short *ngerr;
    short *terr;
    int ger;
    const int dg = 0xff / gmask;

#ifdef WRLIB_DEBUG
    fprintf(stderr, "grayscale dither with %d colors per channel\n", cpc);
#endif
    gerr = (short *)malloc((image->width + 2) * sizeof(short));
    ngerr = (short *)malloc((image->width + 2) * sizeof(short));
    if (!gerr || !ngerr) {
      NFREE(gerr);
      NFREE(ngerr);
      RErrorCode = RERR_NOMEMORY;
      RDestroyXImage(ctx, ximg);
      return NULL;
    }

    memset(gerr, 0, (image->width + 2) * sizeof(short));
    memset(ngerr, 0, (image->width + 2) * sizeof(short));

    for (x = 0, y = 0; x < image->width; x++, y += channels) {
      gerr[x] = (ptr[y] * 30 + ptr[y + 1] * 59 + ptr[y + 2] * 11) / 100;
    }
    gerr[x] = 0;
    /* convert and dither the image to XImage */
    for (y = 0; y < image->height; y++) {
      if (y < image->height - 1) {
        int x1;
        for (x = 0, x1 = (y + 1) * image->width * channels; x < image->width; x++, x1 += channels) {
          ngerr[x] = (ptr[x1] * 30 + ptr[x1 + 1] * 59 + ptr[x1 + 2] * 11) / 100;
        }
        /* last column */
        x1 -= channels;
        ngerr[x] = (ptr[x1] * 30 + ptr[x1 + 1] * 59 + ptr[x1 + 2] * 11) / 100;
      }
      for (x = 0; x < image->width; x++) {
        /* reduce pixel */
        if (gerr[x] > 0xff)
          gerr[x] = 0xff;
        else if (gerr[x] < 0)
          gerr[x] = 0;

        g = table[gerr[x]];

        /*data[ofs] = ctx->colors[g].pixel; */
        XPutPixel(ximg->image, x, y, ctx->colors[g].pixel);
        /* calc error */
        ger = gerr[x] - g * dg;

        /* distribute error */
        g = (ger * 3) / 8;
        /* x+1, y */
        gerr[x + 1] += g;
        /* x, y+1 */
        ngerr[x] += g;
        /* x+1, y+1 */
        ngerr[x + 1] += ger - 2 * g;
      }
      /* skip to next line */
      terr = gerr;
      gerr = ngerr;
      ngerr = terr;
    }
    free(gerr);
    free(ngerr);
  }
  ximg->image->data = (char *)data;

  return ximg;
}

static RXImage *image2Bitmap(RContext *ctx, RImage *image, int threshold)
{
  RXImage *ximg;
  unsigned char *alpha;
  int x, y;

  ximg = RCreateXImage(ctx, 1, image->width, image->height);
  if (!ximg) {
    return NULL;
  }
  alpha = image->data + 3;

  for (y = 0; y < image->height; y++) {
    for (x = 0; x < image->width; x++) {
      XPutPixel(ximg->image, x, y, (*alpha <= threshold ? 0 : 1));
      alpha += 4;
    }
  }

  return ximg;
}

int RConvertImage(RContext *context, RImage *image, Pixmap *pixmap)
{
  RXImage *ximg = NULL;
#ifdef USE_XSHM
  Pixmap tmp;
#endif

  assert(context != NULL);
  assert(image != NULL);
  assert(pixmap != NULL);

  switch (context->vclass) {
    case TrueColor:
      ximg = image2TrueColor(context, image);
      break;

    case PseudoColor:
    case StaticColor:
      if (context->attribs->standard_colormap_mode != RIgnoreStdColormap)
        ximg = image2StandardPseudoColor(context, image);
      else
        ximg = image2PseudoColor(context, image);
      break;

    case GrayScale:
    case StaticGray:
      ximg = image2GrayScale(context, image);
      break;
  }

  if (!ximg) {
    return False;
  }

  *pixmap =
      XCreatePixmap(context->dpy, context->drawable, image->width, image->height, context->depth);

#ifdef USE_XSHM
  if (context->flags.use_shared_pixmap && ximg->is_shared)
    tmp = R_CreateXImageMappedPixmap(context, ximg);
  else
    tmp = None;
  if (tmp) {
    /*
     * We have to copy the shm Pixmap into a normal Pixmap because
     * otherwise, we would have to control when Pixmaps are freed so
     * that we can detach their shm segments. This is a problem if the
     * program crash, leaving stale shared memory segments in the
     * system (lots of them). But with some work, we can optimize
     * things and remove this XCopyArea. This will require
     * explicitly freeing all pixmaps when exiting or restarting
     * wmaker.
     */
    XCopyArea(context->dpy, tmp, *pixmap, context->copy_gc, 0, 0, image->width, image->height, 0,
              0);
    XFreePixmap(context->dpy, tmp);
  } else {
    RPutXImage(context, *pixmap, context->copy_gc, ximg, 0, 0, 0, 0, image->width, image->height);
  }
#else  /* !USE_XSHM */
  RPutXImage(context, *pixmap, context->copy_gc, ximg, 0, 0, 0, 0, image->width, image->height);
#endif /* !USE_XSHM */

  RDestroyXImage(context, ximg);

  return True;
}

/* make the gc permanent (create with context creation).
 * GC creation is very expensive. altering its properties is not. -Dan
 */
int RConvertImageMask(RContext *context, RImage *image, Pixmap *pixmap, Pixmap *mask, int threshold)
{
  GC gc;
  XGCValues gcv;
  RXImage *ximg = NULL;

  assert(context != NULL);
  assert(image != NULL);
  assert(pixmap != NULL);
  assert(mask != NULL);

  if (!RConvertImage(context, image, pixmap))
    return False;

  if (image->format == RRGBFormat) {
    *mask = None;
    return True;
  }

  ximg = image2Bitmap(context, image, threshold);

  if (!ximg) {
    return False;
  }
  *mask = XCreatePixmap(context->dpy, context->drawable, image->width, image->height, 1);
  gcv.foreground = context->black;
  gcv.background = context->white;
  gcv.graphics_exposures = False;
  gc = XCreateGC(context->dpy, *mask, GCForeground | GCBackground | GCGraphicsExposures, &gcv);
  RPutXImage(context, *mask, gc, ximg, 0, 0, 0, 0, image->width, image->height);
  RDestroyXImage(context, ximg);
  XFreeGC(context->dpy, gc);

  return True;
}

Bool RGetClosestXColor(RContext *context, const RColor *color, XColor *retColor)
{
  if (context->vclass == TrueColor) {
    unsigned short rmask, gmask, bmask;
    unsigned short roffs, goffs, boffs;
    unsigned short *rtable, *gtable, *btable;

    roffs = context->red_offset;
    goffs = context->green_offset;
    boffs = context->blue_offset;

    rmask = context->visual->red_mask >> roffs;
    gmask = context->visual->green_mask >> goffs;
    bmask = context->visual->blue_mask >> boffs;

    rtable = computeTable(rmask);
    gtable = computeTable(gmask);
    btable = computeTable(bmask);

    retColor->pixel = (((unsigned long)rtable[color->red]) << roffs) |
                      (((unsigned long)gtable[color->green]) << goffs) |
                      (((unsigned long)btable[color->blue]) << boffs);

    retColor->red = color->red << 8;
    retColor->green = color->green << 8;
    retColor->blue = color->blue << 8;
    retColor->flags = DoRed | DoGreen | DoBlue;

  } else if (context->vclass == PseudoColor || context->vclass == StaticColor) {
    if (context->attribs->standard_colormap_mode != RIgnoreStdColormap) {
      unsigned int *rtable, *gtable, *btable;

      rtable = computeStdTable(context->std_rgb_map->red_mult, context->std_rgb_map->red_max);

      gtable = computeStdTable(context->std_rgb_map->green_mult, context->std_rgb_map->green_max);

      btable = computeStdTable(context->std_rgb_map->blue_mult, context->std_rgb_map->blue_max);

      if (rtable == NULL || gtable == NULL || btable == NULL) {
        RErrorCode = RERR_NOMEMORY;
        return False;
      }

      retColor->pixel = (rtable[color->red] + gtable[color->green] + btable[color->blue] +
                         context->std_rgb_map->base_pixel) &
                        0xffffffff;
      retColor->red = color->red << 8;
      retColor->green = color->green << 8;
      retColor->blue = color->blue << 8;
      retColor->flags = DoRed | DoGreen | DoBlue;

    } else {
      const int cpc = context->attribs->colors_per_channel;
      const unsigned short rmask = cpc - 1; /* different sizes could be used */
      const unsigned short gmask = rmask;   /* for r,g,b */
      const unsigned short bmask = rmask;
      unsigned short *rtable, *gtable, *btable;
      const int cpccpc = cpc * cpc;
      int index;

      rtable = computeTable(rmask);
      gtable = computeTable(gmask);
      btable = computeTable(bmask);

      if (rtable == NULL || gtable == NULL || btable == NULL) {
        RErrorCode = RERR_NOMEMORY;
        return False;
      }
      index = rtable[color->red] * cpccpc + gtable[color->green] * cpc + btable[color->blue];
      *retColor = context->colors[index];
    }

  } else if (context->vclass == GrayScale || context->vclass == StaticGray) {
    const int cpc = context->attribs->colors_per_channel;
    unsigned short gmask;
    unsigned short *table;
    int index;

    if (context->vclass == StaticGray)
      gmask = (1 << context->depth) - 1; /* use all grays */
    else
      gmask = cpc * cpc * cpc - 1;

    table = computeTable(gmask);
    if (!table)
      return False;

    index = table[(color->red * 30 + color->green * 59 + color->blue * 11) / 100];

    *retColor = context->colors[index];
  } else {
    RErrorCode = RERR_INTERNAL;
    return False;
  }

  return True;
}
